package arkham.net;

import java.io.*;
import java.net.*;

import arkham.net.messages.*;
import arkham.net.messages.Message.MessageType;
import arkham.util.AHLogger;

public class ClientBase implements Runnable {

	private int port;
	private String hostname;
	private Socket server;
	private boolean running;
	private String username;
	public String hostusername;
	private String loadString, masterData;
	private MessageEndpoint endpoint;

	/**
	 * Creates a new client to the specified host name and port
	 * @param port
	 * @param hostname
	 */
	public ClientBase()
	{
		running = false;
	}

	/**
	 * Attempts to connect to the server and retrieves the assigned username
	 * @param hostname the hostname of the server
	 * @param port the port
	 * @param preferredName the preferred username
	 * @return true on a success, false otherwise
	 */
	public boolean connect(String hostname, int port, String preferredName)
	{
		this.hostname = hostname;
		this.port = port;

		try
		{
			server = new Socket(hostname, port);
			server.setSoTimeout(5000);
			// handshake: send a username, then receive assigned username
			sendTextMessage(preferredName);
			Message msg = Message.receive(server);
			if (msg instanceof TextMessage)
			{
				this.username = ((TextMessage)msg).getText();
				if (!username.equals(preferredName))
				{
					AHLogger.info("Username conflict: assigned username is " + username);
				}
			}
			else
			{
				throw new NetworkException("Unknown message from server in response to username");
			}
			// Get load string (IMPORTANT!)
			// TODO: download necessary files from server
			sendInfoMessage("loadString");
			msg = Message.receive(server);
			if (msg instanceof InfoMessage)
			{
				InfoMessage imsg = (InfoMessage)msg;
				this.loadString = imsg.getResponse();
			}
			else
			{
				throw new NetworkException("Unknown info message from server in response to loadstring");
			}
			// get master data
			sendInfoMessage("masterData");
			msg = Message.receive(server);
			if (msg instanceof InfoMessage)
			{
				InfoMessage imsg = (InfoMessage)msg;
				this.masterData = imsg.getResponse();
			}
			else
			{
				throw new NetworkException("Unknown info message from server in response to masterdata");
			}
			AHLogger.info("Connected to " + hostname + "!");

		}
		catch (UnknownHostException e) 
		{
			AHLogger.warning("Cannot find host " + hostname);
			return false;
		} 
		catch (IOException e) 
		{
			AHLogger.warning("I/O error connecting to the host " + hostname);
			return false;
		}
		catch (NetworkException e)
		{
			AHLogger.warning("Network exception: " + e.getMessage());
			return false;
		}

		return true;
	}

	public void run()
	{
		if (server == null || !server.isConnected()) return;
		startClient();
		Message msg;

		while (isRunning())
		{		
			try
			{
				msg = Message.receive(getSocket());
				if (msg == null)
				{
					continue;
				}
				else
				{
					messageReceived(msg);
				}
			}
			catch (SocketTimeoutException e)
			{
				continue;
			}
			catch (IOException e)
			{
				stopClient();
			}
		}

		AHLogger.info("Client terminated.");
	}

	/**
	 * Cleans up the connection and any other resources
	 * @throws Exception
	 */
	private void cleanup()
	{
		try {
			server.close();
		} catch (IOException e) {
			AHLogger.error(e.toString());
		}
	}

	/**
	 * Writes a text message to the server
	 * @param msg The message to send
	 * @throws IOException
	 */
	public void sendTextMessage(String msg)
	{
		if (!server.isConnected()) return;
		TextMessage tmsg = new TextMessage(msg);
		boolean result = tmsg.send(server);
		if (!result)
		{
			AHLogger.error("ClientBase.sendTextMessage", "Unable to send text message");
		}
	}
	
	/**
	 * Writes an error message to the server
	 * @param msg The message to send
	 * @throws IOException
	 */
	public void sendErrorMessage(String msg)
	{
		if (!server.isConnected()) return;
		ErrorMessage emsg = new ErrorMessage(msg);
		boolean result = emsg.send(server);
		if (!result)
		{
			AHLogger.error("ClientBase.sendErrorMessage", "Unable to send error message");
		}
	}
	
	/**
	 * Writes a message to the server asking for info
	 * @param request the request to ask for
	 * @throws IOException
	 */
	public void sendInfoMessage(String request)
	{
		if (!server.isConnected()) return;
		InfoMessage imsg = new InfoMessage(request);
		boolean result = imsg.send(server);
		if (!result)
		{
			AHLogger.error("ClientBase.sendInfoMessage", "Unable to send info message");
		}
	}
	
	/**
	 * Writes a message to the server asking for info with specific details
	 * @param request the request to ask for
	 * @param details the details of the request
	 * @throws IOException
	 */
	public void sendInfoMessage(String request, String details)
	{
		if (!server.isConnected()) return;
		InfoMessage imsg = new InfoMessage(request, details);
		boolean result = imsg.send(server);
		if (!result)
		{
			AHLogger.error("ClientBase.sendInfoMessage", "Unable to send info message");
		}
	}
	
	/**
	 * Sends a command message to the server
	 * @param cmsg the command to send
	 */
	public void sendCommandMessage(CommandMessage cmsg)
	{
		if (!server.isConnected()) return;
		boolean result = cmsg.send(server);
		if (!result)
		{
			AHLogger.error("ClientBase.sendCommandMessage", "Unable to send command message");
		}
	}
	
	/**
	 * This is the main handler method for received messages.
	 * This method is only called by the receiver thread
	 * @param msg the message received
	 */
	private void messageReceived(Message msg)
	{
		if (msg.getType().equals(MessageType.TEXT_MESSAGE))
		{
			String text = ((TextMessage)msg).getText();
			if (endpoint != null)
			{
				endpoint.textReceived(text, "server");
			}
		}
		else
		{
			endpoint.messageReceived(msg, "server");
		}
		
	}

	/**
	 * Gets the socket this client is connected on
	 * @return the socket
	 */
	public Socket getSocket()
	{
		return server;
	}

	/**
	 * Gets the port of the connected server
	 * @return the port
	 */
	public int getPort()
	{
		return port;
	}

	/**
	 * Gets the hostname of the connected server
	 * @return the hostname
	 */
	public String getHostname()
	{
		return hostname;
	}
	
	/**
	 * Gets the username of the client
	 * @return the username
	 */
	public String getUsername()
	{
		return username;
	}
	
	/**
	 * Gets the loading string for the game
	 * @return the load string
	 */
	public String getLoadString()
	{
		return loadString;
	}
	
	/**
	 * Gets the master data for the game
	 * @return the master data
	 */
	public String getMasterData()
	{
		return masterData;
	}
	
	/**
	 * sets the username of the host
	 * @param username the username
	 */
	public void setHostUsername(String username)
	{
		this.hostusername = username;
	}
	
	/**
	 * Gets the username of the host
	 * @return the username
	 */
	public String getHostUsername()
	{
		return hostusername;
	}
	
	/**
	 * Sets the message endpoint
	 * @param endpoint the endpoint to set
	 */
	public void setMessageEndpoint(MessageEndpoint endpoint) {
		this.endpoint = endpoint;
	}

	/**
	 * Gets the message endpoint
	 * @return the endpoint
	 */
	public MessageEndpoint getMessageEndpoint() {
		return endpoint;
	}

	/**
	 * @return true if this client is running
	 */
	public synchronized boolean isRunning() {
		return running;
	}

	/**
	 * Starts the client
	 */
	public synchronized void startClient()
	{
		running = true;
	}

	/**
	 * Stops the client
	 */
	public synchronized void stopClient()
	{
		running = false;
	}

}